package com.djz.tool.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 文件操作工具类
 * @author djz
 */
@Slf4j
@Component
public class FileUtil {

    private static final Object LOCK1 = new Object();

    private static final Object LOCK2 = new Object();


    /**
     * 响应文件处理的缓冲区大小，单位：M，默认1M
     */
    private static int bufferedWriterSize;

    /**
     *写行数限制
     */
    private static int writeCount;

    @Value("${transfer.fileUtils.mapObjectLimit.bufferedWriterSize:65536}")
    public void setBufferedWriterSize(int bufferedWriterSize) {
        FileUtil.bufferedWriterSize = bufferedWriterSize;
    }

    @Value("${transfer.fileUtils.mapObjectLimit.writeCount:100000}")
    public void setWriteCount(int writeCount) {
        FileUtil.writeCount = writeCount;
    }
    /**
     * 创建文件
     * @param filePath 文件路径
     * @return 是否创建成功
     */
    public static boolean createFile(String filePath) {
        // 检测没目录时创建目录，优先于try-resource-close
        File file = new File(filePath);
        File dir = file.getParentFile();
        if (!dir.exists()) {
            boolean mk = dir.mkdirs();
            if (mk) {
                log.info("文件目录：{} 创建成功", dir.getAbsolutePath());
            } else {
                // 重新判断file.getParentFile().exists()，否则并发的时候，如果别的线程创建了父目录，就没法往下走了
                if (!dir.exists()) {
                    log.error("文件目录：{} 创建失败，请检查原因", dir.getAbsolutePath());
                }
            }
        }

        if (!file.exists()) {
            try {
                return file.createNewFile();
            } catch (IOException e) {
                log.error("异常：", e);
            }
        } else {
            return true;
        }

        return false;
    }


    /**
     * 删除文件（或文件夹）递归删除具体文件 nio
     * @param fileOrDir 文件实体（文件或目录）
     * @return 是否删除成功
     */
    public static boolean deleteFileOrDir(File fileOrDir) {
        boolean flag = false;
        Path path = Paths.get(fileOrDir.getAbsolutePath());
        try {
            if (fileOrDir.isFile()) {
                Files.deleteIfExists(path);
            } else {
                File[] files = fileOrDir.listFiles();
                if (null == files || files.length == 0) {
                    Files.deleteIfExists(path);
                } else {
                    for (File file: files) {
                        deleteFileOrDir(file);
                    }

                    Files.deleteIfExists(path);
                }
            }
            flag = true;
        } catch (Exception e) {
            log.error("删除文件异常：{}", e.getMessage());
            throw new RuntimeException("删除文件异常", e);
        }

        return flag;
    }

    /**
     * 删除文件（或文件夹）递归删除具体文件 nio
     * @param fileOrDirPath  文件或目录路径
     * @return 是否删除成功
     */
    public static boolean deleteFileOrDir(String fileOrDirPath) {
        return deleteFileOrDir(new File(fileOrDirPath));
    }

    /**
     * 删除该目录/文件及递归下所有比最后修订时间早的文件
     * @param fileOrDirPath 目录
     * @param lastModified 最后修订时间
     * @param isBaseDir 是否不删根目录
     */
    public static void deleteFileOrDir(String fileOrDirPath, Date lastModified, boolean isBaseDir) {
        File distFile = new File(fileOrDirPath);

        if (!distFile.exists()) {
            log.warn("文件：{} 不存在，无法遍历删除", distFile.getAbsolutePath());
            return;
        }

        if (distFile.isDirectory()) {
            File[] files = distFile.listFiles();
            if (files.length == 0 && !isBaseDir) {
                FileUtil.deleteFileOrDir(distFile);
            } else {
                for (File file: files) {
                    deleteFileOrDir(file.getAbsolutePath(), lastModified, false);
                }
            }
        } else {
            Date time = new Date(distFile.lastModified());
            if (time.before(lastModified)) {
                log.info("删除文件：{} ，最后修订时间为：{}", distFile.getAbsolutePath(), time);
                FileUtil.deleteFileOrDir(distFile);
            }
        }
    }

    /**
     * 获取文件内容字节数组
     * @param filePath 文件路径
     * @return 字节数组
     * @throws IOException IO异常
     */
    public static byte[] getBytes(String filePath) throws IOException {
        byte[] bytes = new byte[1024];
        byte[] fileByteArray;

        int len;
        try (ByteArrayOutputStream fileOutputStream = new ByteArrayOutputStream();
             InputStream fileIn = new FileInputStream(filePath)) {

            while ((len = fileIn.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, len);
            }
            fileByteArray = fileOutputStream.toByteArray();
        } catch (IOException e) {
            throw new IOException("文件" + filePath + "转成字节流时异常！", e);
        }

        return fileByteArray;
    }



    /**
     * 获取文件名后缀（扩展名，包含“.”）
     * @param fileName 文件名
     * @return 文件名后缀
     */
    public static String getFileSuffix(String fileName) {
        if (StringUtils.isEmpty(fileName)) {
            return "";
        }

        int index = fileName.lastIndexOf('.');
        if (index == -1) {
            return "";
        }

        return fileName.substring(index);
    }

    /**
     * 根据java.nio.*的流获取文件大小
     * @param file
     */
    public static Long getFileSize(File file) {
        try ( FileInputStream fis = new FileInputStream(file);
              FileChannel fc = fis.getChannel()) {
            if (file.exists() && file.isFile()) {
                return fc.size();
            }
        } catch (Exception e) {
            log.error("err", e);
        }

        return  file.length();
    }



    /**
     * 写字符串信息到文件
     * @param filePath 文件路径
     * @param content 字符串信息
     * @return 文件
     * @throws IOException IO异常
     */
    public static File writeContent(String filePath, String content) throws IOException {
        File file = new File(filePath);
        try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
            out.write(content);
            out.flush();
        }

        return file;
    }

    /**
     * 备份键值对到本地文件
     * @param mapList 键值对数组
     * @param filePath 本地文件路径
     * @throws IOException
     */
    public static void mapToFile(List<Map<String, Object>> mapList, String filePath) throws IOException {
        synchronized (LOCK1) {
            log.debug("写入文件：{}", filePath);
            Set<String> header = mapList.get(0).keySet();
            File file = new File(filePath);
            boolean append = false;
            if (file.exists()) {
                log.debug("文件：{} 存在，采用追加方式更新信息", filePath);
                append = true;
            } else {
                log.debug("文件：{} 不存在，采用新增方式更新信息", filePath);
            }

            boolean created = createFile(filePath);
            if (!created) {
                throw new IOException("文件创建失败");
            }

            try (FileOutputStream fos = new FileOutputStream(file, append);
                 OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
                 BufferedWriter  bw = new BufferedWriter(osw, 65536); ) {

                StringBuilder builder = new StringBuilder();
                int keyLength = header.size();
                int i = 1;

                // 当是全新文件时，才需要增加头部信息,可能存在多次备份追加时头部顺序和个数不一致，后续需留意
                if (!append) {
                    for (String key: header) {
                        builder.append(key);

                        if (i < keyLength) {
                            builder.append("\t");
                        }

                        i++;
                    }
                    builder.append("\r\n");
                    bw.write(builder.toString());
                }

                builder = new StringBuilder("");
                for (int k = 0; k < mapList.size(); k++) {

                    Map<String, Object> map = mapList.get(k);
                    i = 1;

                    for (String key: header) {
                        Object value = map.get(key);
                        if (null == value) {
                            value = "";
                        }

                        builder.append(value);

                        if (i < keyLength) {
                            builder.append("\t");
                        }

                        i++;
                    }

                    builder.append("\r\n");
                    if (i!=0 && k%2000 == 0) {
                        bw.write(builder.toString());
                        builder = new StringBuilder("");
                    }
                }

                if (!StringUtils.isEmpty(builder.toString())) {
                    bw.write(builder.toString());
                }

                log.debug("文件：{} 写入 {} 条数据完成。", filePath, mapList.size());
            }
        }
    }

    /**
     * 从本地备份文件还原成键值对
     * 采用默认gbk编码转换，并利用分隔符\t分隔表头和数据
     * @param filePath 本地备份文件
     * @throws IOException
     */
    public static List<Map<String, Object>> fileToMap(String filePath) throws IOException {
        return fileToMap(filePath, "\t", "gbk");
    }

    /**
     * 从本地备份文件还原成键值对
     * @param filePath 本地备份文件
     * @param srcEncode 编码格式读取
     * @throws IOException IO异常
     */
    public static List<Map<String, Object>> fileToMap(String filePath, String srcEncode) throws IOException {
        return fileToMap(filePath, "\t", srcEncode);
    }

    /**
     * 从本地备份文件还原成键值对
     * @param filePath 本地备份文件
     * @param separator 分隔符
     * @param srcEncode 源编码
     * @throws IOException
     */
    public static List<Map<String, Object>> fileToMap(String filePath, String separator, String srcEncode) throws IOException {
        File file = new File(filePath);
        int i = 1;
        Set<String> keys = new LinkedHashSet<>();
        Map<String, Object> map = null;
        List<Map<String, Object>> mapList = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), srcEncode))) {

            String s = null;
            // 使用readLine方法，一次读一行
            while((s = br.readLine()) != null) {

                // 方法会将后面为空的情况全部舍弃，前面为空的不会舍弃
                String[] arr = s.split(separator, -1);
                if (i == 1) {
                    Collections.addAll(keys, arr);
                } else {
                    map = new LinkedHashMap<>();
                    int index = 0;
                    for (String key: keys) {
                        map.put(key, arr[index]);
                        index++;
                    }
                    mapList.add(map);
                }
                i++;
            }
        } catch(Exception e) {
            log.error("处理文件失败:{}", e.getMessage());
            throw new IOException(e.getMessage());
        }

        return mapList;
    }

    /**
     *
     * @param files 需要合并的文件
     * @param filePath 需要合并的文件的路径
     */
    public static boolean mergeFile(List<File> files, String filePath) {
        boolean flag = false;
        File destFile = new File(filePath);
        // 使用try-with-resource的方式自动关闭流
        try (FileOutputStream fos = new FileOutputStream(destFile)) {
            for (File file : files) {
                if (!file.exists()){
                    continue;
                }

                // 使用try-with-resource的方式自动关闭流
                try (FileInputStream fis = new FileInputStream(file);) {
                    int len = 0;
                    byte[] bufer = new byte[2048];
                    while (-1 != (len = fis.read(bufer)))
                    {
                        fos.write(bufer, 0, len);
                    }
                }
            }
            flag = true;
        } catch (IOException e) {
            log.error("合并文件异常：", e);
        }

        return  flag;
    }

    /**
     *
     * @param srcEncode                 源编码格式
     * @param targetEncode            目标编码格式
     * @param srcPath                     源路径
     * @param targetPath                目标路径
     * @param relativePath              相对于源路径的路径
     * @param srcSeparater                  源分隔符
     * @param targetSeparater                目标分隔符
     * @param filter                         文件类型过滤器
     */
    public static void convertEncoding(String srcEncode, String targetEncode, String srcPath, String targetPath, String srcSeparater, String targetSeparater, String relativePath, FileFilter filter) {
        File srcFile = new File(srcPath);
        if (srcFile.isFile()) {
            String filePath = targetPath+ File.separatorChar + srcFile.getName();
            File targetFile = new File(filePath);
            if (!createFile(filePath)) {
                return;
            }

            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(srcFile), srcEncode));
                 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), targetEncode));) {
                String line;
                while ((line = reader.readLine()) != null) {
                    String s = new String(line.getBytes());
                    if ((!StringUtils.isEmpty(srcSeparater)) && (!StringUtils.isEmpty(targetSeparater))) {
                        s = s.replace(srcSeparater, targetSeparater);
                    }
                    writer.write(s);
                    writer.newLine();
                }
            } catch(Exception e) {
                log.error("编码转换异常：", e);
                throw new RuntimeException("编码转换异常", e);
            }
        } else if (srcFile.isDirectory()) {
            File[] files = srcFile.listFiles(filter);
            // 建立目标目录
            File targetFileDir = new File(targetPath + relativePath);
            if (!targetFileDir.exists()) {
                targetFileDir.mkdirs();
            }
            for (File f : files) {
                convertEncoding(srcEncode, targetEncode, f.getAbsolutePath(), targetFileDir.getAbsolutePath(), srcSeparater, targetSeparater, f.getAbsolutePath().substring(srcPath.length()), filter);
            }
        }
    }

    /**
     * 备份键值对到本地文件
     * @param mapList 键值对数组
     * @param filePath 本地文件路径
     * @throws IOException
     */
    public static void mapObjectToFile(List<Map> mapList, String filePath, String targetEncode) throws IOException {
        synchronized (LOCK2) {
            mapObjectToFileByLimit(mapList, filePath, targetEncode, bufferedWriterSize, writeCount);
        }
    }


    public static void mapObjectToFileByLimit(List mapList, String filePath) throws IOException {
        assignMapObjectToFileByLimit(mapList, filePath, "GBK", bufferedWriterSize, writeCount);
    }

    public static void mapObjectToFileByLimit(List mapList, String filePath, String targetEncode, Integer bufferedWriterSize, Integer writeCount) throws IOException {
        assignMapObjectToFileByLimit(mapList, filePath, targetEncode, bufferedWriterSize, writeCount);
    }

    public static void assignMapObjectToFileByLimit(List<Map<String, Object>> mapList, String filePath, String targetEncode, Integer bufferedWriterSize, Integer writeCount) throws IOException {
        log.debug("写入备份文件：{}", filePath);
        Set<String> header = mapList.get(0).keySet();
        File file = new File(filePath);
        boolean append = false;
        if (file.exists()) {
            log.debug("备份文件：{} 存在，采用追加方式更新备份信息", filePath);
            append = true;
        } else {
            log.debug("备份文件：{} 不存在，采用新增方式更新备份信息", filePath);
        }

        boolean created = createFile(filePath);
        if (!created) {
            return;
        }

        try (FileOutputStream fos = new FileOutputStream(file, append);
             OutputStreamWriter osw = new OutputStreamWriter(fos, targetEncode);
             BufferedWriter bw = new BufferedWriter(osw, bufferedWriterSize); ) {

            StringBuilder builder = new StringBuilder("");
            int keyLength = header.size();
            int i = 1;

            // 当是全新文件时，才需要增加头部信息,可能存在多次备份追加时头部顺序和个数不一致，后续需留意
            if (!append) {
                for (String key: header) {
                    builder.append(key);

                    if (i < keyLength) {
                        builder.append("\t");
                    }

                    i++;
                }
                builder.append("\r\n");
                bw.write(builder.toString());
            }
            builder = new StringBuilder("");
            for (Map<String, Object> map : mapList) {
                i = 1;
                for (String key: header) {
                    Object value = map.get(key);
                    if (null == value) {
                        value = "";
                    } else {
                        // 参数可能是txt转换而来，需要保证内容不包含\t,\r\n
                        // 参数可能是json转换而来，需要保证内容不包含\r\n,\t
                        // 该替换包含后续txt备份还原的所有操作（保证\t,\r\n,\n不出问题）
                        value = value.toString().replace("\t"," ").replace("\r\n", "").replace("\n", "");
                    }

                    builder.append(value);

                    if (i < keyLength) {
                        builder.append("\t");
                    }

                    i++;
                }
                builder.append("\r\n");
                if (i % writeCount  == 0) {
                    bw.write(builder.toString());

                    // 注意builder复位
                    builder = new StringBuilder("");
                }
            }

            if (!StringUtils.isEmpty(builder.toString())) {
                bw.write(builder.toString());
            }

            log.debug("备份文件：{} 写入 {} 条数据完成。", filePath, mapList.size());
        }
    }

    /**
     * 获取一个文件夹下面所有的文件
     * @param file
     * @param fileList
     */
    public static void getAllFile(File file , List<File> fileList) {
        if (!file.exists()) {
            return;
        }

        if (file.isFile()) {
            fileList.add(file);
        } else {
            File[] files = file.listFiles();
            if(null != files && files.length != 0) {
                for(File f : files) {
                    getAllFile(f, fileList);
                }
            }
        }
    }
}
